﻿namespace TfsCloudDeploy.Workflow.Core.Activities
{
    using System;
    using System.Activities;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Drawing;
    using System.IO;
    using System.Text;
    using Microsoft.TeamFoundation.Build.Client;
    using Microsoft.TeamFoundation.Build.Workflow.Activities;
    using Microsoft.TeamFoundation.Build.Workflow.Design;
    using Microsoft.TeamFoundation.Build.Workflow.Tracking;
    using Microsoft.TeamFoundation.Client;
    using Microsoft.TeamFoundation.VersionControl.Client;
    using Microsoft.TeamFoundation.VersionControl.Common;

    [Designer(typeof(TeamBuildBaseActivityDesigner)), SRDescription(typeof(ExtendedWorkflowResources), "CheckFilesInWorkspaceMappings"), BuildActivity(HostEnvironmentOption.All), BuildCategory, ToolboxBitmap(typeof(TeamBuildBaseActivityDesigner), "DefaultBuildIcon.png"), ActivityTracking(ActivityTrackingOption.ActivityOnly)]
    public class CheckFilesInWorkspaceMappings : CodeActivity<bool>
    {
        [SRDescription(typeof(ExtendedWorkflowResources), "SourcesDirectoryDescription"), SRCategory(typeof(ExtendedWorkflowResources), "ActivityCategoryMiscellaneous"), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible), RequiredArgument]
        public InArgument<string> SourcesDirectory { get; set; }

        [SRCategory(typeof(ExtendedWorkflowResources), "ActivityCategoryMiscellaneous"), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible), RequiredArgument]
        public InArgument<List<IWorkspaceMapping>> WorkspaceMappings { get; set; }

        [SRCategory(typeof(ExtendedWorkflowResources), "ActivityCategoryMiscellaneous"), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public InArgument<bool> ShowVerboseInformation { get; set; }

        protected override bool Execute(CodeActivityContext context)
        {
            WorkflowUtility.CreateBuildMessage("CheckFilesInWorkspaceMappings: Validating shelveset item mappings...", context, BuildMessageImportance.High);

            bool isShowVerboseInformation = this.ShowVerboseInformation.Get(context);
            IBuildDetail buildDetail = context.GetExtension<IBuildDetail>();
            BuildShelveset buildShelveset = this.GetGatedCheckinShelvesetForCurrentBuild(context, buildDetail, isShowVerboseInformation);

            if (buildShelveset != null)
            {
                Stopwatch stopwatch = new Stopwatch();
                stopwatch.Start();
                bool isValid = this.AreShelvesetItemsWithinWorkspaceMappings(context, buildDetail, buildShelveset, isShowVerboseInformation);
                stopwatch.Stop();

                WorkflowUtility.CreateBuildMessage(string.Format("CheckFilesInWorkspaceMappings: Mapping validation took {0} seconds.", stopwatch.Elapsed.TotalSeconds), context, BuildMessageImportance.High);

                return isValid;
            }
            else
            {
                WorkflowUtility.CreateBuildMessage("CheckFilesInWorkspaceMappings: Build shelveset not found.", context, BuildMessageImportance.High);
            }

            return false;
        }

        private BuildShelveset GetGatedCheckinShelvesetForCurrentBuild(CodeActivityContext context,
            IBuildDetail buildDetail, bool isShowVerboseInformation)
        {
            if (isShowVerboseInformation)
            {
                WorkflowUtility.CreateBuildMessage(
                    string.Format("CheckFilesInWorkspaceMappings: ShelvesetName={0}.", buildDetail.ShelvesetName),
                    context, BuildMessageImportance.High);
            }

            string originalShelvesetName;
            string originalShelvesetOwner;
            WorkspaceSpec.Parse(buildDetail.ShelvesetName, ".", out originalShelvesetName, out originalShelvesetOwner);

            if (isShowVerboseInformation)
            {
                WorkflowUtility.CreateBuildMessage(
                    string.Format("CheckFilesInWorkspaceMappings: Gated Build Shelveset Name={0}.", originalShelvesetName),
                    context, BuildMessageImportance.High);
                WorkflowUtility.CreateBuildMessage(
                    string.Format("CheckFilesInWorkspaceMappings: Gated Build Shelveset Owner={0}.", originalShelvesetOwner),
                    context, BuildMessageImportance.High);
            }

            if (!string.IsNullOrWhiteSpace(originalShelvesetName) && !string.IsNullOrWhiteSpace(originalShelvesetOwner))
            {
                return new BuildShelveset()
                {
                    ShelvesetName = originalShelvesetName,
                    ShelvesetOwner = originalShelvesetOwner
                };
            }

            return null;
        }

        private bool AreShelvesetItemsWithinWorkspaceMappings(CodeActivityContext context, IBuildDetail buildDetail, BuildShelveset buildShelveset, bool isShowVerboseInformation)
        {
            TfsTeamProjectCollection tfsProjectCollection = buildDetail.BuildServer.TeamProjectCollection;
            VersionControlServer versionControlServer = tfsProjectCollection.GetService<VersionControlServer>();
            PendingSet[] pendingSets = versionControlServer.QueryShelvedChanges(buildShelveset.ShelvesetName, buildShelveset.ShelvesetOwner);
            List<IWorkspaceMapping> workspaceMappings = this.WorkspaceMappings.Get(context); // buildDetail.BuildDefinition.Workspace.Mappings;

            // Normalize/convert the working folder mappings to server mappings (replace $(SourceDir), *, etc...):
            IDictionary<string, string> additionalVariableReplacements = new Dictionary<string, string>();
            string sourcesDirectory = this.SourcesDirectory.Get(context);
            additionalVariableReplacements.Add(VersionControlHelper.SourceDirEnvironmentVariable, sourcesDirectory);

            foreach (IWorkspaceMapping workspaceMapping in workspaceMappings)
            {
                workspaceMapping.LocalItem = VersionControlHelper.ExpandEnvironmentVariables(workspaceMapping.LocalItem, additionalVariableReplacements);                
            }

            if (pendingSets.Length > 0 && workspaceMappings.Count > 0)
            {
                if (isShowVerboseInformation)
                {
                    WorkflowUtility.CreateBuildMessage("CheckFilesInWorkspaceMappings: Workspace Mappings:", context,
                        BuildMessageImportance.High);
                    this.WriteWorkspaceMappingsToLog(context, workspaceMappings);
                }

                WorkingFolder[] folders = GetWorkingFolders(workspaceMappings);

                for (int i = 0; i < pendingSets.Length; i++)
                {
                    PendingSet pendingSet = pendingSets[i];

                    if (pendingSet.Type == PendingSetType.Shelveset && pendingSet.PendingChanges.Length > 0)
                    {
                        for (int j = 0; j < pendingSet.PendingChanges.Length; j++)
                        {
                            PendingChange pendingChange = pendingSet.PendingChanges[j];

                            if (isShowVerboseInformation)
                            {
                                WorkflowUtility.CreateBuildMessage(
                                    string.Format("CheckFilesInWorkspaceMappings: Processing shelved item: {0}.",
                                        pendingChange.ServerItem), context, BuildMessageImportance.High);
                            }

                            bool foundMatch = IsServerPathMapped(pendingChange.ServerItem, true, folders);

                            // If no matching workspace mapping is found, fail and bail out of the first failing item.
                            if (!foundMatch)
                            {
                                WorkflowUtility.CreateBuildMessage(
                                    string.Format("CheckFilesInWorkspaceMappings: Shelved item failed to map to workspace: {0}.",
                                        pendingChange.ServerItem), context, BuildMessageImportance.High);

                                return false;
                            }
                        }
                    }
                }

                return true;
            }

            return false;
        }

        private void WriteWorkspaceMappingsToLog(CodeActivityContext context, List<IWorkspaceMapping> workspaceMappings)
        {
            WorkflowUtility.CreateBuildMessage(string.Format("CheckFilesInWorkspaceMappings: Workspace mappings Count: {0}.", workspaceMappings.Count), context, BuildMessageImportance.High);
            foreach (var workspaceMapping in workspaceMappings)
            {
                WorkflowUtility.CreateBuildMessage(string.Format("CheckFilesInWorkspaceMappings: Workspace Mapping [Type={0}]: {1}.", workspaceMapping.MappingType, workspaceMapping.ServerItem), context, BuildMessageImportance.High);
            }
        }

        private WorkingFolder[] GetWorkingFolders(List<IWorkspaceMapping> workspaceMappings)
        {
            List<WorkingFolder> workingFolders = new List<WorkingFolder>();

            foreach (IWorkspaceMapping workspaceMapping in workspaceMappings)
            {
                WorkingFolderType workingFolderType = (workspaceMapping.MappingType == WorkspaceMappingType.Map) ? WorkingFolderType.Map : WorkingFolderType.Cloak;
                RecursionType recursionType = (workspaceMapping.Depth == WorkspaceMappingDepth.OneLevel) ? RecursionType.OneLevel : RecursionType.Full;
                workingFolders.Add(new WorkingFolder(workspaceMapping.ServerItem, workspaceMapping.LocalItem, workingFolderType, recursionType));
            }

            return workingFolders.ToArray();
        }

        public bool IsServerPathMapped(string serverPath, bool detectImplicitCloak, WorkingFolder[] folders)
        {
            return (null != this.TryGetLocalItemForServerItem(serverPath, detectImplicitCloak, folders));
        }

        public string TryGetLocalItemForServerItem(string serverItem, bool detectImplicitCloak, WorkingFolder[] folders)
        {
            bool flag;
            RecursionType type;

            CheckStringForNullOrEmpty(serverItem, "serverItem");
            string localItem = TranslateServerItemToLocalItem(serverItem, folders, true, out flag, out type);
            if ((detectImplicitCloak && (localItem != null)) && !VersionControlPath.Equals(TryGetServerItemForLocalItem(localItem, folders), serverItem))
            {
                return null;
            }
            return localItem;
        }

        public static string TryGetServerItemForLocalItem(string localItem, WorkingFolder[] folders)
        {
            bool flag;
            RecursionType type;
            CheckStringForNullOrEmpty(localItem, "localItem");
            CheckForNull(folders, "folders");
            string str = TranslateLocalItemToServerItem(localItem, folders, out flag, out type);
            if (flag)
            {
                return null;
            }
            return str;
        }

        private static string TranslateLocalItemToServerItem(string localItem, WorkingFolder[] folders, out bool isCloaked, out RecursionType depth)
        {
            localItem = GetFullPath(localItem, false);
            isCloaked = false;
            depth = RecursionType.None;
            WorkingFolder folder = null;
            int length = 0;
            int num2 = -1;
            string item = null;
            foreach (WorkingFolder folder2 in folders)
            {
                if (((null != folder2) && !folder2.IsCloaked) && (IsSubItem(localItem, folder2.LocalItem) && (folder2.LocalItem.Length > length)))
                {
                    folder = folder2;
                    length = folder2.LocalItem.Length;
                    depth = folder.Depth;
                    if (folder2.Depth == RecursionType.OneLevel)
                    {
                        int folderDepth = GetFolderDepth(folder2.LocalItem);
                        if (num2 < 0)
                        {
                            num2 = GetFolderDepth(localItem);
                        }
                        if ((folderDepth + 1) == num2)
                        {
                            depth = RecursionType.None;
                        }
                        else if (folderDepth != num2)
                        {
                            folder = null;
                        }
                    }
                }
            }
            if (null != folder)
            {
                StringBuilder builder = new StringBuilder(folder.ServerItem, 0x103);
                int relativeStartIndex = GetRelativeStartIndex(localItem, folder.LocalItem);
                if (relativeStartIndex >= 0)
                {
                    builder.Append('/');
                    builder.Append(localItem, relativeStartIndex, localItem.Length - relativeStartIndex);
                    builder.Replace(Path.DirectorySeparatorChar, '/', folder.ServerItem.Length, builder.Length - folder.ServerItem.Length);
                }
                item = VersionControlPath.GetFullPath(builder.ToString());
                int num5 = folder.ServerItem.Length;
                foreach (WorkingFolder folder3 in folders)
                {
                    if (((null != folder3) && folder3.IsCloaked) && ((folder3.ServerItem.Length > num5) && VersionControlPath.IsSubItem(item, folder3.ServerItem)))
                    {
                        isCloaked = true;
                        return item;
                    }
                }
            }
            return item;
        }

        public static void CheckStringForNullOrEmpty(string stringVar, string stringVarName)
        {
            CheckStringForNullOrEmpty(stringVar, stringVarName, false);
        }

        public static void CheckStringForNullOrEmpty(string stringVar, string stringVarName, bool trim)
        {
            CheckForNull(stringVar, stringVarName);
            if (trim)
            {
                stringVar = stringVar.Trim();
            }
            if (stringVar.Length == 0)
            {
                throw new ArgumentException(string.Format("Empty string not allowed: {0}.", stringVarName));
            }
        }

        public static void CheckForNull(object var, string varName)
        {
            if (var == null)
            {
                throw new ArgumentNullException(varName);
            }
        }

        private static string TranslateServerItemToLocalItem(string serverItem, WorkingFolder[] folders, bool matchPattern, out bool isCloaked, out RecursionType depth)
        {
            int num = -1;
            int length = 0;
            WorkingFolder folder = null;
            string str = null;
            depth = RecursionType.Full;
            serverItem = VersionControlPath.GetFullPath(serverItem);
            foreach (WorkingFolder folder2 in folders)
            {
                if (((null != folder2) && VersionControlPath.IsSubItem(serverItem, folder2.ServerItem)) && (folder2.ServerItem.Length > length))
                {
                    folder = folder2;
                    length = folder2.ServerItem.Length;
                    depth = matchPattern ? folder2.Depth : RecursionType.Full;
                    if ((folder2.Depth == RecursionType.OneLevel) && matchPattern)
                    {
                        int folderDepth = VersionControlPath.GetFolderDepth(folder2.ServerItem);
                        if (num < 0)
                        {
                            num = VersionControlPath.GetFolderDepth(serverItem);
                        }
                        if ((folderDepth + 1) == num)
                        {
                            depth = RecursionType.None;
                        }
                        else if (folderDepth != num)
                        {
                            folder = null;
                        }
                    }
                }
            }
            isCloaked = (folder != null) ? folder.IsCloaked : false;
            if ((null == folder) || isCloaked)
            {
                return str;
            }
            StringBuilder builder = new StringBuilder(folder.LocalItem, 260);
            int relativeStartIndex = VersionControlPath.GetRelativeStartIndex(serverItem, folder.ServerItem);
            if (relativeStartIndex >= 0)
            {
                builder.Append(Path.DirectorySeparatorChar);
                builder.Append(serverItem, relativeStartIndex, serverItem.Length - relativeStartIndex);
                builder.Replace('/', Path.DirectorySeparatorChar, folder.LocalItem.Length, builder.Length - folder.LocalItem.Length);
            }
            return GetFullPath(builder.ToString());
        }

        #region FileSpec

        public static int GetRelativeStartIndex(string path, string folder)
        {
            if (path.StartsWith(folder, StringComparison.OrdinalIgnoreCase))
            {
                if (path.Length == folder.Length)
                {
                    return -1;
                }
                if ((folder.Length > 0) && (folder[folder.Length - 1] == '\\'))
                {
                    return folder.Length;
                }
                if (path[folder.Length] == '\\')
                {
                    return (folder.Length + 1);
                }
            }
            return 0;
        }

        public static int GetFolderDepth(string item)
        {
            if (string.IsNullOrEmpty(item))
            {
                return 0;
            }
            int num = 0;
            int length = item.Length;
            for (int i = 0; i < length; i++)
            {
                if (item[i] == Path.DirectorySeparatorChar)
                {
                    num++;
                }
            }
            if (item.StartsWith(@"\\", StringComparison.Ordinal))
            {
                num -= 3;
            }
            else if (((item.Length == 3) && (item[1] == Path.VolumeSeparatorChar)) && (item[2] == Path.DirectorySeparatorChar))
            {
                num--;
            }
            if (num < 0)
            {
                return 0;
            }
            return num;
        }

        public static bool IsSubItem(string item, string parent)
        {
            if (!item.StartsWith(parent, StringComparison.OrdinalIgnoreCase))
            {
                return false;
            }
            return (((item.Length == parent.Length) || ((parent.Length > 0) && (parent[parent.Length - 1] == '\\'))) || (item[parent.Length] == '\\'));
        }

        public static string GetFullPath(string path)
        {
            return GetFullPath(path, true);
        }

        public static string GetFullPath(string path, bool checkForIllegalDollar)
        {
            string fullPathWrapper;
            if (path.Length >= 2)
            {
                char ch = path[path.Length - 1];
                if (((ch == Path.DirectorySeparatorChar) || (ch == Path.AltDirectorySeparatorChar)) && (path[path.Length - 2] != Path.VolumeSeparatorChar))
                {
                    path = path.Substring(0, path.Length - 1);
                }
            }
            string fileName = Path.GetFileName(path);
            if (!Wildcard.IsWildcard(fileName))
            {
                fullPathWrapper = GetFullPathWrapper(path);
            }
            else
            {
                string directoryName = Path.GetDirectoryName(path);
                if (directoryName == null)
                {
                    fullPathWrapper = GetFullPathWrapper(path);
                }
                else
                {
                    if (directoryName.Length == 0)
                    {
                        directoryName = ".";
                    }
                    fullPathWrapper = Path.Combine(GetFullPathWrapper(directoryName), fileName);
                }
            }
            if (checkForIllegalDollar)
            {
                CheckForIllegalDollarInPath(fullPathWrapper);
            }
            return fullPathWrapper;
        }

        public static void CheckForIllegalDollarInPath(string path)
        {
            if (path != null)
            {
                int length = path.Length;
                for (int i = 1; i < length; i++)
                {
                    if ((path[i] == '$') && ((path[i - 1] == Path.DirectorySeparatorChar) || (path[i - 1] == Path.AltDirectorySeparatorChar)))
                    {
                        throw new Exception(string.Format("Invalid path $: {0}", path));
                    }
                }
            }
        }

        private static string GetFullPathWrapper(string path)
        {
            string str2;
            try
            {
                string fullPath = Path.GetFullPath(path);
                if (fullPath.IndexOf('~') >= 0)
                {
                    fullPath = Path.GetFullPath(fullPath);
                }
                str2 = fullPath;
            }
            catch (Exception exception)
            {
                if ((exception is ArgumentException) || (exception is NotSupportedException))
                {
                    throw new Exception(string.Format("Invalid path: {0}.", path), exception);
                }
                if (exception is PathTooLongException)
                {
                    throw new Exception(string.Format("Invalid path/path too long: {0}.", path), exception);
                }
                throw;
            }
            return str2;
        }

        #endregion

        private sealed class BuildShelveset
        {
            public string ShelvesetName { get; set; }

            public string ShelvesetOwner { get; set; }
        }
    }

    internal static class Wildcard
    {
        // Fields
        private static readonly char[] wildcardChars = new char[] { '*', '?' };

        // Methods
        internal static bool IsWildcard(char c)
        {
            if (c != '*')
            {
                return (c == '?');
            }
            return true;
        }

        internal static bool IsWildcard(string str)
        {
            return (str.IndexOfAny(wildcardChars) >= 0);
        }

        internal static bool IsWildcard(string str, int index, int count)
        {
            return (str.IndexOfAny(wildcardChars, index, count) >= 0);
        }

        internal static bool Match(string str, string pattern)
        {
            return Match(str, 0, pattern, 0);
        }

        internal static bool Match(string itemName, int itemIndex, string matchPattern)
        {
            return Match(itemName, itemIndex, matchPattern, 0);
        }

        private static bool Match(string itemName, int itemIndex, string matchPattern, int matchIndex)
        {
            while (matchIndex < matchPattern.Length)
            {
                if (matchPattern[matchIndex] == '*')
                {
                    do
                    {
                        matchIndex++;
                    }
                    while ((matchIndex < matchPattern.Length) && (matchPattern[matchIndex] == '*'));
                    while (!Match(itemName, itemIndex, matchPattern, matchIndex))
                    {
                        if (itemIndex == itemName.Length)
                        {
                            return false;
                        }
                        if (((itemName[itemIndex] == '.') && (itemName.LastIndexOf('.', itemName.Length - 1) == itemIndex)) && ((matchPattern.Length == (matchIndex + 1)) && (matchPattern[matchIndex] == '.')))
                        {
                            return false;
                        }
                        itemIndex++;
                    }
                    return true;
                }
                if (itemIndex == itemName.Length)
                {
                    return (((matchPattern[matchIndex] == '.') && (matchPattern.LastIndexOf('.', matchPattern.Length - 1) == matchIndex)) && Match(itemName, itemIndex, matchPattern, matchIndex + 1));
                }
                if ((string.Compare(itemName, itemIndex, matchPattern, matchIndex, 1, StringComparison.OrdinalIgnoreCase) != 0) && (matchPattern[matchIndex] != '?'))
                {
                    return false;
                }
                itemIndex++;
                matchIndex++;
            }
            return ((itemIndex == itemName.Length) || ((itemName[itemIndex] == '.') && ((itemIndex + 1) == itemName.Length)));
        }
    }
}
